package jdesktop.appbar;

import jdesktop.appbar.win32.WAppBar;

import java.awt.*;

/**
 * Represents the visual aspect of the appbar. The appbar UI comprises the
 * appbar {@link Window} and a reserved area, and of the desktop that hosts the appbar.
 * The size of the reserved area and the appbar window are closely related, and this in turn
 * is related to the appbar model.
 * <h3>Binding</h3>
 * The appbar UI is bound to both the reserved area and the appbar model:
 * <ul>
 * <li>The reserved area and the window size/position are bound bidirectionally.<li>
 * <li>The window size/position and the appbar model are bound bidirectionally.</lI>
 * </ul>
 * Changes to any one of the reserved area, the window bounds or the model,
 * typically affects the others. For the most part, this is desirable behavior.
 * <p>
 * When this behaviour is not desirable, the binding can be disabled using the properties
 * {@link #updateReservedArea} and {@link #updateWindow}. }For example, when updating
 * many properties of the model, to avoid flicker, the window position should be updated
 * only after the last property has set, and not for each property change. This can be accompanied
 * by setting the {@link #updateWindow} property.
 * 
 */
public abstract class AppBar
{
    /**
     * Controls the binding between the window position and the reserved area.
     * When {@code true}
     */
    static public final String UPDATE_RESERVED_AREA = "updateReservedArea";

    /**
     * Controls the binding between the window position and the model.
     * When enabled, changes to either the window position or the model affect the other.
     * When disabled, there is no binding between the two.
     * When enabled after being disabled, no changes are made to the model or window.
     * Instead, the client should call one of
     * [@link #setPositionFromModel} or {@link #setPositionFromWindow()}
     *
     */
    static public final String UPDATE_WINDOW = "updateWindow";

    /**
     * The model that is bound to this appbar.
     * @return
     */
    public abstract AppBarModel getAppBarModel();


    /**
     * Determines if area has been reserved for the appbar. If no space has been reserved,
     * the appbar window is just a regular window.
     * @return
     */
    public abstract boolean isAreaReserved();

    /**
     * Fetches the area of the desktop that is reserved for this appbar.
     * @param rect  The rectangle to receive the bounds. Can be null, in which case
     *  a new rectangle is allocated.
     * @return
     * If the area has not been reserved, this returns the area that would be reserved.
     * Note that this area is not always the same size as the window, if insets hiding is
     * used.
     */
    public abstract Rectangle getReservedArea(Rectangle rect);
    
    /**
     * Computes the desired rectangle for the appbar for a given barSize and edge, and
     * the insets to add to this rectangle to get the final window position.
     * When inset hiding is disabled, the window rect is equal to the appbar area.
     * When inset hiding is enabled, the appbar rect does not include the insets from 3 sides.
     *
     * @param result        The rectangle that receives the result. This is the size the appbar
     *      area should be set to.
     * @param screenSize    The screen size (or region to contain the appbar.)
     * @param edge          The edge the bar occupies.
     * @param barSize       The desired client size of the appbar.
     * @param fullInsets    The actual insets of the window. This is used to compute the appbar
     *      rectangle.
     * @param hideInsets    The state of which insets are hidden.
     * @return The result rectangle, {@cpde result}
     */
    static public Rectangle computeBounds(Rectangle result, Rectangle screenSize, Edge edge, int barSize,
                                Insets fullInsets, Insets revealHiddenInsets, Sides hideInsets)
    {
        result.setBounds(screenSize);

        // we leave the primary axis (the length of the toolbar) to the the same as the corresponding
        // edge of the screen.
        Orientation primary = edge.getOrientation();

        int size = barSize;

        // visible insets are added fully,
        // hidden insets have their reveal part added
        int inset = GeomUtil.getInset(fullInsets, edge);
        if (hideInsets.isSet(edge))
            size += inset-GeomUtil.getInset(revealHiddenInsets, edge);
        else
            size += inset;  // visible inset

        inset = GeomUtil.getInset(fullInsets, edge.getOppositeEdge());
        if (hideInsets.isSet(edge.getOppositeEdge()))
            size += inset-GeomUtil.getInset(revealHiddenInsets, edge.getOppositeEdge());
        else
            size += inset;  // visible inset

        // set length of secondary axis.
        Extent extent = GeomUtil.getExtent(result, primary);
        GeomUtil.sizeExtent(extent, edge.isOrigin(), size);
        GeomUtil.setExtent(result, primary, extent);
        return result;
    }

    /**
     * The window associated with this appbar.
     * @return
     */
    public abstract Window getWindow();

    /**
     * If true, the reserved area is updated as necessary to reflect changes in the
     * appbar size and position.
     *
     * @return
     */
    public abstract boolean isUpdateReservedArea();

    /**
     * Sets the new value for the {@link #updateReservedArea} property.
     * @param updateReservedArea
     * @return
     *
     * The reserved area is only updated if the number of calls to this method with
     * {@code }true} and {@code }false} values are the same.
     */
    public abstract void setUpdateReservedArea(boolean updateReservedArea);

    /**
    * Retrieves the float activation bounds. The appbar is floated when dragged into this area of the screen,
    * and docked when dragged out of this area.
    * @return
    */
    public abstract Rectangle getFloatableDragBounds();

    public abstract void setFloatableDragBounds(Rectangle floatableRectangle);

    /**
     * Fetches the bounds of the appbar window.
     * @param rect
     * @return
     */
    public Rectangle getBounds(Rectangle rect)
    {
        // todo - why is this not just the window bounds?
        rect = GeomUtil.initRect(rect);
        if (getAppBarModel().isDocked())
            rect = getDockedBounds(rect);
        else
            rect = getFloatingBounds(rect);
        return rect;
    }

    /**
     * The bounds of the window when in the floating state.
     * @param rect
     * @return
     */
    public abstract Rectangle getFloatingBounds(Rectangle rect);

    /**
     * Retrieves the actual or expected bounds of the window
     * @param rect
     * @return
     */
    public abstract Rectangle getDockedBounds(Rectangle rect);


    /**
     * Sets the appbar position from the window.  
     * @return
     */
    protected abstract Rectangle setPositionFromWindow();

    /**
     * Sets the window position from the appbar model.
     */
    public abstract void setPositionFromModel();

    public abstract void setUpdateWindow(boolean b);
    public abstract boolean isUpdateWindow();


    static AppBar createAppBar()
    {
        return new WAppBar();
    }
}
